#include <render\levels\level.h>
#include <render\threads\viewResolver.h>

#include <app\states\modelInfoState.h>

PFNGLGENBUFFERSARBPROC pglGenBuffersARB = 0;
PFNGLBINDBUFFERARBPROC pglBindBufferARB = 0;
PFNGLBUFFERDATAARBPROC pglBufferDataARB = 0;
PFNGLBUFFERSUBDATAARBPROC pglBufferSubDataARB = 0;
PFNGLDELETEBUFFERSARBPROC pglDeleteBuffersARB = 0;
PFNGLGETBUFFERPARAMETERIVARBPROC pglGetBufferParameterivARB = 0;
PFNGLMAPBUFFERARBPROC pglMapBufferARB = 0;
PFNGLUNMAPBUFFERARBPROC pglUnmapBufferARB = 0;

#define glGenBuffersARB           pglGenBuffersARB
#define glBindBufferARB           pglBindBufferARB
#define glBufferDataARB           pglBufferDataARB
#define glBufferSubDataARB        pglBufferSubDataARB
#define glDeleteBuffersARB        pglDeleteBuffersARB
#define glGetBufferParameterivARB pglGetBufferParameterivARB
#define glMapBufferARB            pglMapBufferARB
#define glUnmapBufferARB          pglUnmapBufferARB

boost::shared_ptr<tile> * 
level::searchForTile(vec2i tile_id) {

  vector<boost::shared_ptr<tile> *>::iterator i = curr_loaded_tiles->begin();

  for (; i != curr_loaded_tiles->end(); i++) {
    bool found = (*i)->get()->equals(tile_id);
    if (found) {
      return (*i);
    }
  }

  return NULL;
}

boost::shared_ptr<tile> * 
level::searchForTileAndPop(vec2i tile_id) {
  
  vector<boost::shared_ptr<tile> *>::iterator i = curr_loaded_tiles->begin();

  for (; i != curr_loaded_tiles->end(); i++) {
    bool found = (*i)->get()->equals(tile_id);
    if (found) {
      boost::shared_ptr<tile> *t = (*i);
      curr_loaded_tiles->erase(i);
      return t;
    }
  }

  return NULL;
}

void 
level::updateVisibility(viewFrustum vf) {

  for (int i = 0; i != curr_loaded_tiles->size(); i++) {
    (*curr_loaded_tiles)[i]->get()->setVisible(vf);
  }

}

void 
level::updateTextures(GLubyte * dst, int tile_x, int tile_y) {
  if (!dst) return;

  unsigned char **tex_rows = getTextureRows(tile_x, tile_y);
  memset(dst, 255, PBO_SIZE * PBO_SIZE * 3);
  if (tex_rows) {
    memcpy(dst, tex_rows, 256 * 256 * 3);
  }

}

void 
level::updateLoadedTiles(viewFrustum vf) {

  // identify window size in tiles count in x and y
  vec2i d = (vp_state.info.tiled_window_size + 2) / 2;
  vec2i size = mi_state->info().terr_tile_size;
  vec2i to_pos_l = to_position.to2d() / (size * pow(2.0, level_));
  
  vector<boost::shared_ptr<tile> *> used_tiles;
  for (int dy = -d[1]; dy <= d[1]; dy++) {
    for (int dx = -d[0]; dx <= d[0]; dx++) {
      vec2i tile_id = to_pos_l + vec2i(dx, dy);
      
      boost::shared_ptr<tile> *founded_tile = searchForTileAndPop(tile_id);
      if (!founded_tile) {
        boost::shared_ptr<tile> *new_tile = new boost::shared_ptr<tile>(new tile(all_loaded_tiles, to_pos_l, tile_id, start_position.to2d(), patch_size, level_, mi_state));
        founded_tile = new_tile;
      }

      founded_tile->get()->setVisible(vf);
      used_tiles.push_back(founded_tile);
    }
  }

  // erase not used tiles
  for (int i = 0; i != curr_loaded_tiles->size(); i++) {
    delete (*curr_loaded_tiles)[i];
  }

  curr_loaded_tiles->clear();
  curr_loaded_tiles->swap(used_tiles);

}

s_download_query
level::generateDownloadQuery() {
  
  s_download_query selected_query;
  s_download_query query;

  for (int i = 0; i != curr_loaded_tiles->size(); i++) {
    query = (*curr_loaded_tiles)[i]->get()->getDownloadQuery();
    if ((selected_query.request_type == e_request_type::NOTHING || query.distance < selected_query.distance) && query.visible) {
      selected_query = query;
      selected_query.tile_ptr = (*curr_loaded_tiles)[i];
    }
  }

  return selected_query;
}

bool 
level::prepare(vec3f to_position_, viewFrustum to_vf, bool can_continue, int top_level) {

  // discard preparation of this level due to performance reasons
  if (!can_continue) {
    to_level_status = DISCARD;
    memset(mapped_gpu_data, 255, PBO_SIZE * PBO_SIZE * 3);
    return false;
  }

  to_position = to_position_;
  //std::cout << "to pos: " << fixed << to_position[0] << " " << fixed << to_position[1] << std::endl;
  to_center = (to_position.to2dr() / (pow_2_level * 2)) * 2;

  updateVisibility(to_vf);

  constructLevel(to_vf);

  // check if the position for this level has changed
  vec3f tmp_curr_positionf = move_state->getCurrentPosition();
  vec2i tmp_curr_position = (tmp_curr_positionf.to2dr() / (pow_2_level * 2)) * 2;
  if (tmp_curr_position != to_center || level_ == top_level) to_level_status = STOP;
  else to_level_status = CONTINUE;

  return (to_level_status == CONTINUE);
}

unsigned short **
level::getTerrainRows(int tile_x, int tile_y) {
  boost::shared_ptr<tile> * req_tile = searchForTile(vec2i(tile_x, tile_y));

  if (req_tile) return req_tile->get()->getTerrainRows();

  return NULL;
}

unsigned char **
level::getTextureRows(int tile_x, int tile_y) {
  boost::shared_ptr<tile> * req_tile = searchForTile(vec2i(tile_x, tile_y));

  if (req_tile) return req_tile->get()->getTextureRows();

  return NULL;
}

void
level::constructLevel(viewFrustum vf) {

  if (to_center != current_center) {

    updateLoadedTiles(vf);

    unsigned short **rows = NULL;
    unsigned char **texture_rows = NULL;

    int prev_tile_y = -1000, prev_tile_x = -1000;
    vec2i terr_tile_size = mi_state->info().terr_tile_size;

    int tile_x, tile_y, offset_x, offset_y;
    int render_x, render_y;
    int align_y, align_x;

    // construct current patch from appropriate tiles
    for (int y = 0; y < patch_size[1]; y++) {

      tile_y = (to_center[1] - half_patch_size + y + 1) / terr_tile_size[1];

      for (int x = 0; x < patch_size[0]; x++) {

        tile_x = (to_center[0] - half_patch_size + x + 1) / terr_tile_size[1];

        if (prev_tile_y != tile_y || prev_tile_x != tile_x) {
          prev_tile_y = tile_y;
          prev_tile_x = tile_x;
          rows = getTerrainRows(tile_x, tile_y);
          texture_rows = getTextureRows(tile_x, tile_y);
        }

        if (x == 0 || x == (patch_size[0] - 1)) align_y = (y % 2);
        else align_y = 0;

        offset_y = (to_center[1] - half_patch_size + y - align_y + 1) % terr_tile_size[1];
        render_y = (to_center[1] - half_patch_size + y - align_y + 1) * pow_2_level;

        if (y == 0 || y == (patch_size[1] - 1)) align_x = (x % 2);
        else align_x = 0;

        offset_x = (to_center[0] - half_patch_size + x - align_x + 1) % mi_state->info().terr_tile_size[0];
        render_x = (to_center[0] - half_patch_size + x - align_x + 1) * pow_2_level;

        vertices[y * patch_size[0] * 3 + x * 3 + 0] = (GLfloat) -(render_x - start_position.x);
        vertices[y * patch_size[0] * 3 + x * 3 + 1] = (GLfloat) -(render_y - start_position.y);

        if (rows && offset_y >=0 && offset_x >= 0) { 
          vertices[y * patch_size[0] * 3 + x * 3 + 2] = -rows[offset_y][offset_x] / 10.0;

          if (texture_rows) {
            for (int ty = 0; ty != 8; ty++) {
              for (int tx = 0; tx != 8; tx++) {
                mapped_gpu_data[(y * 8 + ty) * PBO_SIZE * 3 + (x * 8 + tx) * 3 + 0] = texture_rows[(offset_y + align_y) * 8 + ty][((offset_x + align_x) * 8 + tx) * 3 + 0];
                mapped_gpu_data[(y * 8 + ty) * PBO_SIZE * 3 + (x * 8 + tx) * 3 + 1] = texture_rows[(offset_y + align_y) * 8 + ty][((offset_x + align_x) * 8 + tx) * 3 + 1];
                mapped_gpu_data[(y * 8 + ty) * PBO_SIZE * 3 + (x * 8 + tx) * 3 + 2] = texture_rows[(offset_y + align_y) * 8 + ty][((offset_x + align_x) * 8 + tx) * 3 + 2];
              }
            }
          }
        }
        else {
          vertices[y * patch_size[0] * 3 + x * 3 + 2] = 0;
        }
      }
    }
    data_changed = true;
  }
}

void 
level::render(bool can_upload_gpu_data) {
  
  if (can_upload_gpu_data && data_changed) {

    if (to_level_status != DISCARD) {

      glBindTexture(GL_TEXTURE_2D, textureId);
      glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, texturePboId);

      glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); // release pointer to mapping buffer

      glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, PBO_SIZE, PBO_SIZE, GL_RGB, GL_UNSIGNED_BYTE, 0);

      glBindBufferARB(GL_ARRAY_BUFFER_ARB, verticesVboId);
      glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat) * vertices_count * 3, vertices, GL_STREAM_DRAW);

      first_render = false;
      data_changed = false;

      glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, PBO_SIZE * PBO_SIZE * 3, 0, GL_STREAM_DRAW_ARB);
      mapped_gpu_data = (GLubyte*) glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB);

    }
  }

  if (can_upload_gpu_data) {
    current_center = to_center;
    current_position = to_position;
    curr_level_status = to_level_status;

    vec2i child_center = (current_position.to2dr() / pow_2_level) * 2;
    vec2i this_center = (current_position.to2dr() / (pow_2_level * 2)) * 2;

    if (2 * this_center.y != child_center.y && (2 * this_center.x == child_center.x)) Lshape = 0;
    if (2 * this_center.y != child_center.y && (2 * this_center.x != child_center.x)) Lshape = 1;
    if (2 * this_center.y == child_center.y && (2 * this_center.x == child_center.x)) Lshape = 2;
    if (2 * this_center.y == child_center.y && (2 * this_center.x != child_center.x)) Lshape = 3;
  }

  if (!first_render && curr_level_status != DISCARD) {
    glBindTexture(GL_TEXTURE_2D, textureId);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER_ARB, verticesVboId);
    glVertexPointer(3, GL_FLOAT, 0, 0);

    glBindBufferARB(GL_ARRAY_BUFFER_ARB, texcoordsVboId);
    glTexCoordPointer(2, GL_FLOAT, 0, 0);

    glColor3f(1.0f, 1.0f, 1.0f);
    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indicesVboId[BOTTOM]);
    glIndexPointer(GL_UNSIGNED_INT, 0, 0);
    glDrawElements(GL_TRIANGLE_STRIP, indices_count[BOTTOM], GL_UNSIGNED_INT, 0);

    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indicesVboId[LEFT]);
    glIndexPointer(GL_UNSIGNED_INT, 0, 0);
    glDrawElements(GL_TRIANGLE_STRIP, indices_count[LEFT], GL_UNSIGNED_INT, 0);
    
    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indicesVboId[RIGHT]);
    glIndexPointer(GL_UNSIGNED_INT, 0, 0);
    glDrawElements(GL_TRIANGLE_STRIP, indices_count[RIGHT], GL_UNSIGNED_INT, 0);

    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indicesVboId[TOP]);
    glIndexPointer(GL_UNSIGNED_INT, 0, 0);
    glDrawElements(GL_TRIANGLE_STRIP, indices_count[TOP], GL_UNSIGNED_INT, 0);

    // center part for stop/top level
    if (curr_level_status == STOP) {
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indicesVboId[CENTER]);
      glIndexPointer(GL_UNSIGNED_INT, 0, 0);
      glDrawElements(GL_TRIANGLE_STRIP, indices_count[CENTER], GL_UNSIGNED_INT, 0);
    }
    else {
      // L shaped region
      //glColor3f(1.0f, 1.0f, 0.0f);
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, LshapedIndicesVboId[Lshape]);
      glIndexPointer(GL_UNSIGNED_INT, 0, 0);
      glDrawElements(GL_TRIANGLE_STRIP, LshapedIndices_count[Lshape], GL_UNSIGNED_INT, 0);
    }

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
  }
}

level::level(int _level, vector<vector<boost::shared_ptr<tile> *> > *all_loaded_tiles_, viewingParametersState *vp_state_) {
  level_ = _level;
  curr_level_status = CONTINUE;

  all_loaded_tiles = all_loaded_tiles_;
  curr_loaded_tiles = &(*all_loaded_tiles_)[level_];

  // get needed states handlers
  vp_state.copy(vp_state_);
  dyn_vp_state = GET_STATE(viewingParametersState, streamingClient::instance().connection());
  move_state = GET_STATE(moveState, streamingClient::instance().connection());
  mi_state = GET_STATE(modelInfoState, streamingClient::instance().connection());

  patch_size = vec2i(127, 127);//(vp_state.info.tiled_window_size * mi_state->info().terr_tile_size) - 1;
  half_patch_size = patch_size[0] / 2;
  pow_2_level = (int) pow(2.0, level_);

  start_position = move_state->getStartPosition();

  // get VBO functions adresses
  glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB");
  glBindBufferARB = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB");
  glBufferDataARB = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB");
  glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)wglGetProcAddress("glBufferSubDataARB");
  glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB");
  glGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)wglGetProcAddress("glGetBufferParameterivARB");
  glMapBufferARB = (PFNGLMAPBUFFERARBPROC)wglGetProcAddress("glMapBufferARB");
  glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)wglGetProcAddress("glUnmapBufferARB");

  // create texture and pixel buffer object
  glGenTextures(1, &textureId);
  glBindTexture(GL_TEXTURE_2D, textureId);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  glTexImage2D(GL_TEXTURE_2D, 0, 3, PBO_SIZE, PBO_SIZE, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);//(GLvoid*)texData);

  glGenBuffersARB(1, &texturePboId);
  glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, texturePboId);
  glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, PBO_SIZE * PBO_SIZE * 3, 0, GL_STREAM_DRAW_ARB);

  mapped_gpu_data = (GLubyte *) glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB);
  if (mapped_gpu_data) {
    memset(mapped_gpu_data, 30 + 50 * level_, PBO_SIZE * PBO_SIZE * 3);
  }
  glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);

  // generate VBO buffers
  glGenBuffersARB(1, &verticesVboId);
  glGenBuffersARB(1, &normalsVboId);
  glGenBuffersARB(1, &texcoordsVboId);
  glGenBuffersARB(5, &indicesVboId[0]);
  glGenBuffersARB(4, &LshapedIndicesVboId[0]);
  
  vertices_count = patch_size[0] * patch_size[1];
  vertices = new GLfloat[vertices_count * 3];
  
  normals = NULL;

  texcoords_count = patch_size[0] * patch_size[1];
  texcoords = new GLfloat[texcoords_count * 2];

  generateTextureCoordinates();

  glBindBufferARB(GL_ARRAY_BUFFER_ARB, texcoordsVboId);
  glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat) * texcoords_count * 2, texcoords, GL_STATIC_DRAW);

  createBasePatch();
  createLShapes();

  current_center = vec2i(-1000, -1000);
  data_changed = false;
  first_render = true;

  keep_discard_state = 20;
}

void 
level::generateTextureCoordinates() {
  vec2i pow2patch_size = patch_size + 1;
  int align_x, align_y;

  for (int y = 0; y != patch_size[1]; y++) {
    float v = (float) y / (float) pow2patch_size[1];
    for (int x = 0; x != patch_size[0]; x++) {
      float u = (float) x / (float) pow2patch_size[0];

      if (x == 0 || x == (patch_size[0] - 1)) align_y = (y % 2);
      else align_y = 0;

      if (y == 0 || y == (patch_size[1] - 1)) align_x = (x % 2);
      else align_x = 0;

      texcoords[y * patch_size[0] * 2 + x * 2 + 0] = u - (1.0 * align_x / pow2patch_size[0]);
      texcoords[y * patch_size[0] * 2 + x * 2 + 1] = v - (1.0 * align_y / pow2patch_size[1]);
    }
  }
}

void
level::createBasePatch() {

  // BOTTOM
  int inc = -1;
  int index = 0;
  int idx = 0;

  indices_count[BOTTOM] = 2 * patch_size[0] * ((patch_size[1] / 4));
  indices[BOTTOM]  = new GLuint[indices_count[BOTTOM]];

  for (int r = 0; r < ((patch_size[1] / 4)); r++) {
    inc *= -1;
    for (int c = 0; c < patch_size[0]; c++) {
      indices[BOTTOM][idx++] = index;
      indices[BOTTOM][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, indicesVboId[BOTTOM]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices_count[BOTTOM], indices[BOTTOM], GL_STATIC_DRAW);

  // LEFT
  inc = -1;
  index = patch_size[0] * (patch_size[1] / 4);
  idx = 0;

  indices_count[LEFT] = 2 * ((patch_size[0] / 4) + 1) * (2 * ((patch_size[1] / 4) + 1));
  indices[LEFT]  = new GLuint[indices_count[LEFT]];

  for (int r = 0; r < ( 2 * ((patch_size[1] / 4) + 1)); r++) {
    inc *= -1;
    for (int c = 0; c < (patch_size[0] / 4) + 1; c++) {
      indices[LEFT][idx++] = index;
      indices[LEFT][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, indicesVboId[LEFT]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices_count[LEFT], indices[LEFT], GL_STATIC_DRAW);

  // RIGHT
  inc = -1;
  index = patch_size[0] * (patch_size[1] / 4) + (patch_size[0] - (patch_size[0] / 4) - 1);
  idx = 0;
  indices_count[RIGHT] = 2 * ((patch_size[0] / 4) + 1) * (2 * ((patch_size[1] / 4) + 1));
  indices[RIGHT]  = new GLuint[indices_count[RIGHT]];

  for (int r = 0; r < ( 2 * ((patch_size[1] / 4) + 1)); r++) {
    inc *= -1;
    for (int c = 0; c < (patch_size[0] / 4) + 1; c++) {
      indices[RIGHT][idx++] = index;
      indices[RIGHT][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, indicesVboId[RIGHT]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices_count[RIGHT], indices[RIGHT], GL_STATIC_DRAW);

  // TOP
  inc = -1;
  index =  patch_size[0] * (patch_size[1] - (patch_size[1] / 4) - 1);
  idx = 0;

  indices_count[TOP] = 2 * patch_size[0] * ((patch_size[1] / 4));
  indices[TOP]  = new GLuint[indices_count[TOP]];

  for (int r = 0; r < (patch_size[1] / 4); r++) {
    inc *= -1;
    for (int c = 0; c < patch_size[0]; c++) {
      indices[TOP][idx++] = index;
      indices[TOP][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, indicesVboId[TOP]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices_count[TOP], indices[TOP], GL_STATIC_DRAW);

  // CENTER
  inc = -1;
  index = patch_size[0] * (patch_size[1] / 4) + (patch_size[0] / 4);
  idx = 0;

  indices_count[CENTER] = (2 * (patch_size[0] - 2 * (patch_size[0] / 4))) * (patch_size[1] - 2 * (patch_size[1] / 4));
  indices[CENTER]  = new GLuint[indices_count[CENTER]];

  for (int r = 0; r < (patch_size[1] - 2 * (patch_size[1] / 4)); r++) {
    inc *= -1;
    for (int c = 0; c < (patch_size[0] - 2 * (patch_size[0] / 4)); c++) {
      indices[CENTER][idx++] = index;
      indices[CENTER][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, indicesVboId[CENTER]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices_count[CENTER], indices[CENTER], GL_STATIC_DRAW);

}

void
level::createLShapes() {

  // BOTTOM-RIGHT
  int inc = -1;
  int index =  patch_size[0] * (patch_size[1] / 4) + (patch_size[0] / 4);
  int idx = 0;

  LshapedIndices_count[0] = (2 * (patch_size[0] - 2 * (patch_size[0] / 4)))  +  (2 * 2 * (2 * (patch_size[1] / 4) + 1));
  LshapedIndices[0] = new GLuint[LshapedIndices_count[0]];

  for (int r = 0; r < 1; r++) {
    inc *= -1;
    for (int c = 0; c < patch_size[0] - 2 * (patch_size[0] / 4); c++) {
      LshapedIndices[0][idx++] = index;
      LshapedIndices[0][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  inc = 1;
  for (int r = 0; r < 2 * (patch_size[1] / 4) + 1; r++) {
    inc *= -1;
    for (int c = 0; c < 2; c++) {
      LshapedIndices[0][idx++] = index;
      LshapedIndices[0][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, LshapedIndicesVboId[0]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * LshapedIndices_count[0], LshapedIndices[0], GL_STATIC_DRAW);

  // BOTTOM-LEFT
  inc = 1;
  index =  patch_size[0] * ((patch_size[1] / 4) + 1) - (patch_size[0] / 4) - 1;
  idx = 0;

  LshapedIndices_count[1] = (2 * (patch_size[0] - 2 * (patch_size[0] / 4)))  +  (2 * 2 * (2 * (patch_size[1] / 4) + 1));
  LshapedIndices[1] = new GLuint[LshapedIndices_count[1]];

  for (int r = 0; r < 1; r++) {
    inc *= -1;
    for (int c = 0; c < patch_size[0] - 2 * (patch_size[0] / 4); c++) {
      LshapedIndices[1][idx++] = index;
      LshapedIndices[1][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  inc = -1;
  for (int r = 0; r < 2 * (patch_size[1] / 4) + 1; r++) {
    inc *= -1;
    for (int c = 0; c < 2; c++) {
      LshapedIndices[1][idx++] = index;
      LshapedIndices[1][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, LshapedIndicesVboId[1]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * LshapedIndices_count[1], LshapedIndices[1], GL_STATIC_DRAW);

  // TOP-RIGHT
  inc = -1;
  index =  patch_size[0] * ((patch_size[1] / 4) + 1) - (patch_size[0] / 4) - 2;
  idx = 0;

  LshapedIndices_count[2] = (2 * (patch_size[0] - 2 * (patch_size[0] / 4)))  +  (2 * 2 * (2 * (patch_size[1] / 4) + 1));
  LshapedIndices[2] = new GLuint[LshapedIndices_count[2]];

  for (int r = 0; r < (2 * (patch_size[1] / 4) + 1); r++) {
    inc *= -1;
    for (int c = 0; c < 2; c++) {
      LshapedIndices[2][idx++] = index;
      LshapedIndices[2][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  inc = 1;
  for (int r = 0; r < 1; r++) {
    inc *= -1;
    for (int c = 0; c < patch_size[0] - 2 * (patch_size[0] / 4); c++) {
      LshapedIndices[2][idx++] = index;
      LshapedIndices[2][idx++] = index + patch_size[0];
      index += inc;
    }
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, LshapedIndicesVboId[2]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * LshapedIndices_count[2], LshapedIndices[2], GL_STATIC_DRAW);

  // TOP-LEFT
  inc = 1;
  index =  patch_size[0] * (patch_size[1] / 4) + (patch_size[0] / 4) + 1;
  idx = 0;

  LshapedIndices_count[3] = (2 * (patch_size[0] - 2 * (patch_size[0] / 4)))  +  (2 * 2 * (2 * (patch_size[1] / 4) + 1));
  LshapedIndices[3] = new GLuint[LshapedIndices_count[3]];

  for (int r = 0; r < (2 * (patch_size[1] / 4) + 1); r++) {
    inc *= -1;
    for (int c = 0; c < 2; c++) {
      LshapedIndices[3][idx++] = index;
      LshapedIndices[3][idx++] = index + patch_size[0];
      index += inc;
    }
    index += (patch_size[0] - inc);
  }

  inc = -1;
  for (int r = 0; r < 1; r++) {
    inc *= -1;
    for (int c = 0; c < patch_size[0] - 2 * (patch_size[0] / 4); c++) {
      LshapedIndices[3][idx++] = index;
      LshapedIndices[3][idx++] = index + patch_size[0];
      index += inc;
    }
  }

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, LshapedIndicesVboId[3]);
  glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * LshapedIndices_count[3], LshapedIndices[3], GL_STATIC_DRAW);
}

level::~level() {
  for (int i = 0; i != curr_loaded_tiles->size(); i++) {
    delete (*curr_loaded_tiles)[i];
  }

  if (vertices) delete [] vertices;
  if (normals) delete [] normals;
  if (texcoords) delete [] texcoords;

  for (int i = 0; i != 5; i++) {
    if (indices[i]) delete [] indices[i];
  }

  for (int i = 0; i != 4; i++) {
    if (LshapedIndices[i]) delete [] LshapedIndices[i];
  }

  pglDeleteBuffersARB(1, &verticesVboId);
  pglDeleteBuffersARB(1, &normalsVboId);
  pglDeleteBuffersARB(1, &texcoordsVboId);
  pglDeleteBuffersARB(5, &indicesVboId[0]);
  pglDeleteBuffersARB(4, &LshapedIndicesVboId[0]);
}
